﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace RayTracer.Kernels
{
    /// <summary>
    /// Beinhaltet den OpenCL Code der Kernel.
    /// </summary>
    partial class KernelPrimitiveSphere
    {
        private const string KERNEL_MAIN = "PrimitiveSphere";

        /*************
        *  
        *  Eingabe:
        *  rayPos      Eingansstrahlursprungsposition          [i]
        *  rayDir      Eingangsstrahlrichtung                  [i]
        *  oldDist     Die alten min. Distanzen.               [i]
        *  
        *  objCount    Anzahl der Objekte M.
        *  objPos      Position der Kugel.                     [m]
        *  objRot      Rotation der Kugel.                     [m]
        *  objMat      Material der Kugel                      [m]
        *  objRadius   Radius der Kugel.                       [m]
        *  
        * 
        *  Ausgabe:
        *  dist        Die neue Distanz.                       [i]
        *  mat         Das neue Material                       [i]
        *  intersec    Der Schnittpunkt.                       [i]
        *  normal      Der Normalenvektor am Schnittpunkt.     [i]
        *  
        *************/
        private const string KERNEL_SOURCE = @"
            kernel void PrimitiveSphere(
                global  read_only float4* rayPos,
				global  read_only float4* rayDir,
				global  read_only float* lastDist,
                 
				int objCount,
				global  read_only float4* objPos,
				global  read_only float4* objRot,
				global  read_only float16* objMat,
				global  read_only float* objRadius,

				global  write_only float* dist,
				global  write_only float16* mat,
				global  write_only float4* intersec,
				global  write_only float4* normal
            )
            {
	            int i = get_global_id(0);
	            float lDist = lastDist[i];
                rayPos[i] = rayPos[i] + 0.0001f*rayDir[i];
	            for(int m = 0; m < objCount; m++) {
	                float s = -1.0f; 
								
		            // M = Mittelpunkt
		            // O = Startpunkt der Geraden
		            // r = Richtungsvektor der Geraden

		            // a= r_1^2 + r_2^2 + r_3^2
		            // b= 2((O_1-M_1)r_1+(O_2-M_2)r_2+(O_3-M_3)r_3)
		            // c= ((O_1-M_1)^2 + (O_2-M_2)^2 + (O_3-M_3)^2) - radius^2
                    float4 v = rayPos[i] - objPos[m];// (O-M) Vektor vom Mittelpunkt der Kugel zum Ausgangspunkt des Strahls
		            float a = rayDir[i].x * rayDir[i].x + rayDir[i].y * rayDir[i].y + rayDir[i].z * rayDir[i].z;
		            float b = 2.0f * dot(v, rayDir[i]);
		            float c = v.x * v.x +
			                  v.y * v.y +
			                  v.z * v.z - 
			                  objRadius[m] * objRadius[m];

	                // Determinante der Mitternachtsformel
	                // Wenn Determinante kleiner 0 existierte keine Lösung, somit kein Schnittpunkt
		            float det = b * b - 4 * a * c;

	                // Gesuchte Schrittlänge bis zu existierendem Schnittpunkt
	                // s wird in Geradegleichung des Richtungsvektors eingesetzt um Schnittpunkt definieren zu können
                    // s_1,2=(-b±v(b^2-4ac))/2a
		            if(det < 0.0f){
			            // Kein Schnittpunkt, wenn Determinanten kleiner 0
			            continue;
		            } else if (det == 0.0f) {
			            // Ein Schnittpunkt, wenn Determinante gleich 0
			            s = (-b) / (2 * a);
		            } else if (det > 0.0f) {
			            // Zwei Schnittpunkte, wenn Determinante größer 0
			            // Kleineres der beiden s gesucht, welches größer 0 ist
			            // negatives s bedeutet, dass Schnittpunkt hinter dem Ausgangspunkt liegt
			            float s1 = (-b + sqrt(det)) / (2 * a);
			            float s2 = (-b - sqrt(det)) / (2 * a);

			            // Wenn beide Werte negativ sind, liegen beide Schnittpunkte hinter dem Ausgangspunkt
			            // und befinden sich nicht im sichtbaren Bereich
			            if (s1 <= -0.01f && s2 <= -0.01f) {
				            continue;
			            } else if (s1 > 0.01f && s2 <= -0.01f) {
				            // Wenn s2<=0 aber s1 positiv ist, dann ist s1 als einziger Wert von Interesse
				            s = s1;
			            } else if (s1 <= -0.01f && s2 > 0.01f) {
				            // Wenn s1<=0 aber s2 positiv ist, dann ist s2 als einziger Wert von Interesse
				            s = s2;
			            } else {
				            // Wenn vorhergehende Prüfungen nicht zutreffen, sind beide Werte positiv und der kleiner von beiden wird gesucht
                            s=s1 < s2 ? s1 : s2;
			            }
		            }

		            // s wird in Geradengleichung für Strahl eingesetzt um Schnittpunkt zu definieren
		            // SP = Schnittpunkt
		            // O = Ortsvektor der Geraden
		            // r = Richtungsvektor der Geraden
		            // SP = O+s*r
		            float4 SP = rayPos[i] + s * rayDir[i];
                    SP.w = 0.0f;

		            // Distanz zwischen Ausgangspunkt und gefundenem Schnittpunkt
		            float thisDist = length(SP - rayPos[i]);

		            if((thisDist >  0.0f && thisDist < lDist) || lDist == 0.0f) {
			            lDist = thisDist;
			            mat[i] = objMat[m];
			            intersec[i] = SP;

			            // Normalenvektor des Punktes auf der Kugel: SP-M
			            float4 normDir = SP - objPos[m];

			            // Normalenvektor: Normalen-Richtung mit der Länge 1
			            normal[i] = normDir / length(normDir);
		            }
	            }
	            dist[i] = lDist;
            }
        ";
    }
}
